-
Notifications
You must be signed in to change notification settings - Fork 16
Responsive dashboard #1255
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Responsive dashboard #1255
Conversation
WalkthroughReplaces three static statistic tiles with Dialog-driven modals (Users, Instruments, Records); wires three new data hooks, local modal state, animated modal lists, loading/error handling, and a Records/Sessions trend view; adds click-to-navigate on the Subjects tile. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Dashboard
participant DataHooks as DataHooks
participant DialogUI as DialogUI
participant Navigator
User->>Dashboard: Clicks "Users"/"Instruments"/"Records" tile
Dashboard->>DialogUI: open dialog (show Spinner while loading)
Dashboard->>DataHooks: call hook (useUsersQuery / useInstrumentInfoQuery / useInstrumentRecords)
DataHooks-->>Dashboard: return data or loading/error
Dashboard->>DialogUI: render animated list (AnimatePresence/motion) and trend chart
DialogUI-->>User: display modal content
User->>DialogUI: selects item
DialogUI->>Dashboard: emit item click event
Dashboard->>Navigator: navigate to route (e.g., /datahub)
Navigator-->>User: route change
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Pre-merge checks and finishing touches❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (5)
apps/web/src/routes/_app/dashboard.tsx (5)
33-38: Redundantenabled: true.Queries are enabled by default in TanStack Query. This can be removed unless you need it for future conditional fetching.
🔎 Suggested fix
const recordsQuery = useInstrumentRecords({ - enabled: true, params: { groupId: currentGroup?.id } });
54-62: Consider memoizing derived data to avoid recomputation on every render.
recordCounteriterates overrecordIdsfor each instrument (O(n×m)). For larger datasets, this could cause unnecessary re-renders.🔎 Suggested optimization
+import { useMemo } from 'react'; + +// Inside component: +const recordCounter = useMemo(() => { + if (!recordsQuery.data || !instrumentInfo) return []; + const counts = new Map<string, number>(); + for (const record of recordsQuery.data) { + counts.set(record.instrumentId, (counts.get(record.instrumentId) ?? 0) + 1); + } + return instrumentInfo.map((inst) => ({ + count: counts.get(inst.id) ?? 0, + instrumentTitle: inst.title + })); +}, [recordsQuery.data, instrumentInfo]);
278-305: Static header insideAnimatePresenceand potentially non-unique key.The header row (lines 279-288) shouldn't be inside
AnimatePresencesince it's not animated. Also,instrument.titlemay not be unique—preferinstrument.idas the key.🔎 Suggested fix
<ul className="flex flex-col gap-5 overflow-auto"> + <div className="flex justify-between gap-4 font-bold"> + <p>{t({ en: 'Title', fr: 'Titre' })}</p> + <p>{t({ en: 'Kind', fr: 'Genre' })}</p> + </div> + <hr /> <AnimatePresence mode="popLayout"> - <div className="flex justify-between gap-4 font-bold"> - ... - </div> - <hr></hr> {instrumentInfo?.map((instrument, i) => { return ( <motion.li ... - key={instrument.title} + key={instrument.id}
336-365: Same structural issues as instruments modal.Static header is inside
AnimatePresence, andinstrument.instrumentTitlemay not be unique. Apply similar fixes: move header outsideAnimatePresenceand use a unique key (e.g., combine title with index if no ID available). Also consider adding loading state forrecordsQuery.
214-214: Staggered animation delay may feel sluggish with many items.
delay: 0.15 * iaccumulates—20 items means the last one starts animating after 3 seconds. Consider capping the delay or reducing the multiplier for snappier UX.🔎 Example cap
- transition={{ bounce: 0.2, delay: 0.15 * i, duration: 1.5, type: 'spring' }} + transition={{ bounce: 0.2, delay: Math.min(0.1 * i, 0.5), duration: 0.8, type: 'spring' }}
📜 Review details
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/web/src/routes/_app/dashboard.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: lint-and-test
🔇 Additional comments (1)
apps/web/src/routes/_app/dashboard.tsx (1)
1-31: LGTM!Imports and state declarations are well-organized.
| <ul className="flex flex-col gap-5 overflow-auto"> | ||
| <AnimatePresence mode="popLayout"> | ||
| {userInfoQuery.data?.map((user, i) => { | ||
| return ( | ||
| <motion.li | ||
| layout | ||
| animate={{ opacity: 1, y: 0 }} | ||
| exit={{ opacity: 0 }} | ||
| initial={{ opacity: 0 }} | ||
| key={user.username} | ||
| transition={{ bounce: 0.2, delay: 0.15 * i, duration: 1.5, type: 'spring' }} | ||
| > | ||
| <div className="flex justify-between gap-4"> | ||
| <p>{user.username}</p> | ||
| </div> | ||
| </motion.li> | ||
| ); | ||
| })} | ||
| </AnimatePresence> | ||
| </ul> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing loading/error states in modal content.
If userInfoQuery is still fetching or fails, the modal displays empty content with no feedback. Consider adding loading spinners or error messages.
🔎 Example addition
<ul className="flex flex-col gap-5 overflow-auto">
+ {userInfoQuery.isLoading && <p>Loading...</p>}
+ {userInfoQuery.isError && <p>Failed to load users</p>}
<AnimatePresence mode="popLayout">
{userInfoQuery.data?.map((user, i) => {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <ul className="flex flex-col gap-5 overflow-auto"> | |
| <AnimatePresence mode="popLayout"> | |
| {userInfoQuery.data?.map((user, i) => { | |
| return ( | |
| <motion.li | |
| layout | |
| animate={{ opacity: 1, y: 0 }} | |
| exit={{ opacity: 0 }} | |
| initial={{ opacity: 0 }} | |
| key={user.username} | |
| transition={{ bounce: 0.2, delay: 0.15 * i, duration: 1.5, type: 'spring' }} | |
| > | |
| <div className="flex justify-between gap-4"> | |
| <p>{user.username}</p> | |
| </div> | |
| </motion.li> | |
| ); | |
| })} | |
| </AnimatePresence> | |
| </ul> | |
| <ul className="flex flex-col gap-5 overflow-auto"> | |
| {userInfoQuery.isLoading && <p>Loading...</p>} | |
| {userInfoQuery.isError && <p>Failed to load users</p>} | |
| <AnimatePresence mode="popLayout"> | |
| {userInfoQuery.data?.map((user, i) => { | |
| return ( | |
| <motion.li | |
| layout | |
| animate={{ opacity: 1, y: 0 }} | |
| exit={{ opacity: 0 }} | |
| initial={{ opacity: 0 }} | |
| key={user.username} | |
| transition={{ bounce: 0.2, delay: 0.15 * i, duration: 1.5, type: 'spring' }} | |
| > | |
| <div className="flex justify-between gap-4"> | |
| <p>{user.username}</p> | |
| </div> | |
| </motion.li> | |
| ); | |
| })} | |
| </AnimatePresence> | |
| </ul> |
🤖 Prompt for AI Agents
apps/web/src/routes/_app/dashboard.tsx around lines 204-223: the modal list
renders nothing when userInfoQuery is loading or errored; update the JSX to
conditionally render a loading state (use userInfoQuery.isLoading to show a
spinner/placeholder), an error state (use userInfoQuery.isError and show
userInfoQuery.error.message or a friendly message), and an explicit empty-state
when data is an empty array; only render the AnimatePresence/motion list when
userInfoQuery.data?.length > 0, and ensure the loading/error/empty states are
accessible (visible text and/or aria-live) so users get immediate feedback.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (4)
apps/web/src/routes/_app/dashboard.tsx (4)
30-31: Consider filtering instrument query by group.
useInstrumentInfoQueryis called without params, but later filtered in-memory bycurrentGroup.accessibleInstrumentIds(lines 40-44). If the API supports group-based filtering, passingparams: { groupId: currentGroup?.id }would reduce payload size and improve performance.
40-62: Consider memoizing expensive data transformations.The
instrumentData,instrumentInfo, andrecordCountercomputations run on every render. IfrecordsQuery.dataorinstrumentInfoQuery.datacontain many items, the filtering and counting operations (especially lines 56-62) could impact performance. Consider wrapping these inuseMemowith appropriate dependencies.🔎 Example using useMemo
+import React, { useMemo, useState } from 'react'; - const instrumentData = currentGroup - ? instrumentInfoQuery.data?.filter((instrument) => { - return currentGroup.accessibleInstrumentIds.includes(instrument.id); - }) - : instrumentInfoQuery.data; + const instrumentData = useMemo( + () => + currentGroup + ? instrumentInfoQuery.data?.filter((instrument) => { + return currentGroup.accessibleInstrumentIds.includes(instrument.id); + }) + : instrumentInfoQuery.data, + [currentGroup, instrumentInfoQuery.data] + ); - const instrumentInfo = instrumentData?.map((instrument) => { - return { - id: instrument.id, - kind: instrument.kind, - title: instrument.details.title - }; - }); + const instrumentInfo = useMemo( + () => + instrumentData?.map((instrument) => ({ + id: instrument.id, + kind: instrument.kind, + title: instrument.details.title + })), + [instrumentData] + ); - const recordIds = recordsQuery.data?.map((record) => record.instrumentId); + const recordIds = useMemo(() => recordsQuery.data?.map((record) => record.instrumentId), [recordsQuery.data]); - const recordCounter = - instrumentInfo?.map((title) => { - return { - count: recordIds?.filter((val) => val === title.id).length ?? 0, - instrumentTitle: title.title - }; - }) ?? []; + const recordCounter = useMemo( + () => + instrumentInfo?.map((title) => ({ + count: recordIds?.filter((val) => val === title.id).length ?? 0, + instrumentTitle: title.title + })) ?? [], + [instrumentInfo, recordIds] + );
204-232: Add empty state for when no users exist.The modal now handles loading and error states well (addressing the previous review). However, if
userInfoQuery.datais an empty array, the modal displays only the header with no feedback. Consider adding an empty state message.🔎 Example empty state
<ul className="flex flex-col gap-5 overflow-auto"> {userInfoQuery.isLoading && <Spinner />} {userInfoQuery.isError && ( <p> {t({ en: 'Error finding users', fr: "erreur lors de la recherche d'utilisateurs" })} </p> )} + {userInfoQuery.data?.length === 0 && ( + <p> + {t({ + en: 'No users found', + fr: 'Aucun utilisateur trouvé' + })} + </p> + )} <AnimatePresence mode="popLayout"> {userInfoQuery.data?.map((user, i) => {
217-223: Cap animation delays for large lists.The staggered animation delay
0.15 * i(lines 223, 306, 365) could result in very long delays for large lists. For example, 50 items would delay the last item by 7.5 seconds. Consider capping the delay (e.g.,Math.min(0.15 * i, 1.5)) or removing the stagger for lists beyond a certain threshold.🔎 Example with capped delay
transition={{ bounce: 0.2, delay: Math.min(0.15 * i, 1.5), duration: 1.5, type: 'spring' }}Also applies to: 300-306, 359-365
📜 Review details
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/web/src/routes/_app/dashboard.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/src/routes/_app/dashboard.tsx (3)
apps/web/src/hooks/useInstrumentInfoQuery.ts (1)
useInstrumentInfoQuery(15-29)apps/web/src/hooks/useUsersQuery.ts (1)
useUsersQuery(17-19)apps/web/src/hooks/useInstrumentRecords.ts (1)
useInstrumentRecords(12-29)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: lint-and-test
🔇 Additional comments (2)
apps/web/src/routes/_app/dashboard.tsx (2)
236-256: LGTM: Proper semantic button with navigation.Using a
buttonelement for the clickable Subjects card is semantically correct and accessible. The navigation implementation is straightforward.
33-38: No action needed—the API handles undefinedgroupIdgracefully.When
groupIdis undefined, Prisma ignores it in the where clause (lines 245, 286 ofInstrumentRecordsService), returning all records the user can access rather than throwing an error or matching NULL values. The unconditionalenabled: trueis safe.
| <ul className="flex flex-col gap-5 overflow-auto"> | ||
| <AnimatePresence mode="popLayout"> | ||
| <div className="flex justify-between gap-4 font-bold"> | ||
| <p> | ||
| {t({ | ||
| en: 'Title', | ||
| fr: 'Titre' | ||
| })} | ||
| </p>{' '} | ||
| <p>{t({ en: 'Kind', fr: 'Genre' })}</p> | ||
| </div> | ||
| <hr></hr> | ||
| {instrumentInfo?.map((instrument, i) => { | ||
| return ( | ||
| <motion.li | ||
| layout | ||
| animate={{ opacity: 1, y: 0 }} | ||
| exit={{ opacity: 0 }} | ||
| initial={{ opacity: 0 }} | ||
| key={instrument.title} | ||
| transition={{ bounce: 0.2, delay: 0.15 * i, duration: 1.5, type: 'spring' }} | ||
| > | ||
| <div className="flex justify-between gap-4"> | ||
| <p>{instrument.title}</p> <p>{instrument.kind}</p> | ||
| </div> | ||
| </motion.li> | ||
| ); | ||
| })} | ||
| </AnimatePresence> | ||
| </ul> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add loading/error/empty states and use stable keys.
Similar to the Users modal, this should handle:
- Loading state when
instrumentInfoQuery.isLoading - Error state when
instrumentInfoQuery.isError - Empty state when
instrumentInfois an empty array
Additionally, using instrument.title as the key (line 305) could cause issues if titles aren't unique. Use instrument.id instead for a stable, unique key.
🔎 Proposed fix
<ul className="flex flex-col gap-5 overflow-auto">
+ {instrumentInfoQuery.isLoading && <Spinner />}
+ {instrumentInfoQuery.isError && (
+ <p>
+ {t({
+ en: 'Error loading instruments',
+ fr: 'Erreur de chargement des instruments'
+ })}
+ </p>
+ )}
+ {instrumentInfo?.length === 0 && (
+ <p>
+ {t({
+ en: 'No instruments available',
+ fr: 'Aucun instrument disponible'
+ })}
+ </p>
+ )}
<AnimatePresence mode="popLayout">
<div className="flex justify-between gap-4 font-bold">
<p>
{t({
en: 'Title',
fr: 'Titre'
})}
</p>{' '}
<p>{t({ en: 'Kind', fr: 'Genre' })}</p>
</div>
<hr></hr>
{instrumentInfo?.map((instrument, i) => {
return (
<motion.li
layout
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0 }}
initial={{ opacity: 0 }}
- key={instrument.title}
+ key={instrument.id}
transition={{ bounce: 0.2, delay: 0.15 * i, duration: 1.5, type: 'spring' }}
>
<div className="flex justify-between gap-4">
<p>{instrument.title}</p> <p>{instrument.kind}</p>
</div>
</motion.li>
);
})}
</AnimatePresence>
</ul>Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In apps/web/src/routes/_app/dashboard.tsx around lines 286 to 315, the list
rendering currently lacks loading, error and empty states and uses an unstable
key (instrument.title). Update the JSX to: check instrumentInfoQuery.isLoading
and render a compact loading indicator; check instrumentInfoQuery.isError and
render an error message or retry control; if instrumentInfo is an empty array
render an empty-state message; and when mapping instruments use a stable unique
key (instrument.id) instead of instrument.title; keep the AnimatePresence/motion
list structure and use the same layout/transition props when rendering each
instrument.
| <ul className="flex flex-col gap-5 overflow-auto"> | ||
| <AnimatePresence mode="popLayout"> | ||
| <div className="flex justify-between gap-4 font-bold"> | ||
| <p> | ||
| {t({ | ||
| en: 'Title', | ||
| fr: 'Titre' | ||
| })} | ||
| </p>{' '} | ||
| <p>{t({ en: 'Number', fr: 'Numero' })}</p> | ||
| </div> | ||
| <hr></hr> | ||
| {recordCounter?.map((instrument, i) => { | ||
| return ( | ||
| <motion.li | ||
| layout | ||
| animate={{ opacity: 1, y: 0 }} | ||
| exit={{ opacity: 0 }} | ||
| initial={{ opacity: 0 }} | ||
| key={instrument.instrumentTitle} | ||
| transition={{ bounce: 0.2, delay: 0.15 * i, duration: 1.5, type: 'spring' }} | ||
| > | ||
| <div className="flex justify-between gap-4"> | ||
| <p>{instrument.instrumentTitle}</p> <p>{instrument.count}</p> | ||
| </div> | ||
| </motion.li> | ||
| ); | ||
| })} | ||
| </AnimatePresence> | ||
| </ul> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add loading/error/empty states and improve keys.
The Records modal should handle loading/error/empty states for recordsQuery. Additionally, using instrument.instrumentTitle as the key (line 364) may not be unique. Since recordCounter is derived from instrumentInfo, consider preserving id in the recordCounter objects and using it as the key.
🔎 Proposed fix
First, update the recordCounter computation to include id:
const recordCounter =
instrumentInfo?.map((title) => {
return {
count: recordIds?.filter((val) => val === title.id).length ?? 0,
+ id: title.id,
instrumentTitle: title.title
};
}) ?? [];Then update the modal:
<ul className="flex flex-col gap-5 overflow-auto">
+ {recordsQuery.isLoading && <Spinner />}
+ {recordsQuery.isError && (
+ <p>
+ {t({
+ en: 'Error loading records',
+ fr: 'Erreur de chargement des enregistrements'
+ })}
+ </p>
+ )}
+ {recordCounter.length === 0 && !recordsQuery.isLoading && (
+ <p>
+ {t({
+ en: 'No records found',
+ fr: 'Aucun enregistrement trouvé'
+ })}
+ </p>
+ )}
<AnimatePresence mode="popLayout">
<div className="flex justify-between gap-4 font-bold">
<p>
{t({
en: 'Title',
fr: 'Titre'
})}
</p>{' '}
<p>{t({ en: 'Number', fr: 'Numero' })}</p>
</div>
<hr></hr>
{recordCounter?.map((instrument, i) => {
return (
<motion.li
layout
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0 }}
initial={{ opacity: 0 }}
- key={instrument.instrumentTitle}
+ key={instrument.id}
transition={{ bounce: 0.2, delay: 0.15 * i, duration: 1.5, type: 'spring' }}
>
<div className="flex justify-between gap-4">
<p>{instrument.instrumentTitle}</p> <p>{instrument.count}</p>
</div>
</motion.li>
);
})}
</AnimatePresence>
</ul>Committable suggestion skipped: line range outside the PR's diff.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (2)
apps/web/src/routes/_app/dashboard.tsx (2)
286-315: Missing loading/error states and unstable key.This modal still lacks:
- Loading state when
instrumentInfoQuery.isLoading- Error state when
instrumentInfoQuery.isError- Empty state when
instrumentInfois emptyAlso,
instrument.title(line 305) may not be unique. Useinstrument.idas the key.Proposed fix
<ul className="flex flex-col gap-5 overflow-auto"> + {instrumentInfoQuery.isLoading && <Spinner />} + {instrumentInfoQuery.isError && ( + <p>{t({ en: 'Error loading instruments', fr: 'Erreur de chargement des instruments' })}</p> + )} <AnimatePresence mode="popLayout"> <div className="flex justify-between gap-4 font-bold"> ... </div> <hr></hr> {instrumentInfo?.map((instrument, i) => { return ( <motion.li ... - key={instrument.title} + key={instrument.id} ... >
345-374: Missing loading/error states and unstable key.This modal also needs:
- Loading state when
recordsQuery.isLoading- Error state when
recordsQuery.isError- Empty state when
recordCounteris emptyUse
instrument.idas the key (line 364) after adding it torecordCounter.Proposed fix
<ul className="flex flex-col gap-5 overflow-auto"> + {recordsQuery.isLoading && <Spinner />} + {recordsQuery.isError && ( + <p>{t({ en: 'Error loading records', fr: 'Erreur de chargement des enregistrements' })}</p> + )} <AnimatePresence mode="popLayout"> ... {recordCounter?.map((instrument, i) => { return ( <motion.li ... - key={instrument.instrumentTitle} + key={instrument.id} ... >
🧹 Nitpick comments (1)
apps/web/src/routes/_app/dashboard.tsx (1)
56-62: IncludeidinrecordCounterfor stable list keys.The
recordCounterobjects lack anidfield. Without it, the Records modal must useinstrumentTitleas a key, which may not be unique. Include theidto enable stable React keys downstream.Proposed fix
const recordCounter = instrumentInfo?.map((title) => { return { count: recordIds?.filter((val) => val === title.id).length ?? 0, + id: title.id, instrumentTitle: title.title }; }) ?? [];
📜 Review details
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/web/src/routes/_app/dashboard.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/src/routes/_app/dashboard.tsx (1)
vendor/zod@3.x/src/v3.d.ts (1)
record(2499-2499)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: lint-and-test
🔇 Additional comments (1)
apps/web/src/routes/_app/dashboard.tsx (1)
236-256: LGTM!Using a semantic
<button>element with navigation on click is appropriate. Thevoidkeyword properly handles the returned promise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (2)
apps/web/src/routes/_app/dashboard.tsx (2)
287-316: Add loading/error/empty states.This modal still lacks loading, error, and empty state handling for
instrumentInfoQuery. Without these, users see no feedback during data fetch or on failure.🔎 Recommended fix
<ul className="flex flex-col gap-5 overflow-auto"> + {instrumentInfoQuery.isLoading && <Spinner />} + {instrumentInfoQuery.isError && ( + <p> + {t({ + en: 'Error loading instruments', + fr: 'Erreur de chargement des instruments' + })} + </p> + )} + {instrumentInfo?.length === 0 && !instrumentInfoQuery.isLoading && ( + <p> + {t({ + en: 'No instruments available', + fr: 'Aucun instrument disponible' + })} + </p> + )} <AnimatePresence mode="popLayout"> <div className="flex justify-between gap-4 font-bold">
346-375: Add loading/error/empty states and use stable key.This modal lacks loading/error/empty state handling and uses
instrument.instrumentTitleas the key (line 365), which may not be unique. Useinstrument.idinstead since it's already included inrecordCounter(line 59).🔎 Recommended fix
<ul className="flex flex-col gap-5 overflow-auto"> + {recordsQuery.isLoading && <Spinner />} + {recordsQuery.isError && ( + <p> + {t({ + en: 'Error loading records', + fr: 'Erreur de chargement des enregistrements' + })} + </p> + )} + {recordCounter.length === 0 && !recordsQuery.isLoading && ( + <p> + {t({ + en: 'No records found', + fr: 'Aucun enregistrement trouvé' + })} + </p> + )} <AnimatePresence mode="popLayout"> <div className="flex justify-between gap-4 font-bold"> <p> {t({ en: 'Title', fr: 'Titre' })} </p>{' '} <p>{t({ en: 'Number', fr: 'Numero' })}</p> </div> <hr></hr> {recordCounter?.map((instrument, i) => { return ( <motion.li layout animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0 }} initial={{ opacity: 0 }} - key={instrument.instrumentTitle} + key={instrument.id} transition={{ bounce: 0.2, delay: 0.15 * i, duration: 1.5, type: 'spring' }} > <div className="flex justify-between gap-4"> <p>{instrument.instrumentTitle}</p> <p>{instrument.count}</p> </div> </motion.li> ); })} </AnimatePresence> </ul>
🧹 Nitpick comments (1)
apps/web/src/routes/_app/dashboard.tsx (1)
30-38: Consider enabling queries conditionally.All queries run immediately on mount, even though the modals start closed. For better performance, consider enabling each query only when its modal opens.
🔎 Example optimization
- const instrumentInfoQuery = useInstrumentInfoQuery(); + const instrumentInfoQuery = useInstrumentInfoQuery({ + enabled: isInstrumentModalOpen + }); - const userInfoQuery = useUsersQuery(); + const userInfoQuery = useUsersQuery({ + enabled: isUserModalOpen + }); const recordsQuery = useInstrumentRecords({ - enabled: true, + enabled: isRecordModalOpen, params: { groupId: currentGroup?.id } });Note: Verify that the hooks support an
enabledoption.
📜 Review details
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/web/src/routes/_app/dashboard.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/src/routes/_app/dashboard.tsx (3)
apps/web/src/hooks/useInstrumentInfoQuery.ts (1)
useInstrumentInfoQuery(15-29)apps/web/src/hooks/useUsersQuery.ts (1)
useUsersQuery(17-19)apps/web/src/hooks/useInstrumentRecords.ts (1)
useInstrumentRecords(12-29)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: lint-and-test
🔇 Additional comments (5)
apps/web/src/routes/_app/dashboard.tsx (5)
1-16: LGTM!All necessary imports are present for the modal interactions, animations, and data fetching.
27-29: LGTM!Modal state management is straightforward and correct.
40-63: LGTM!Data processing logic is correct with appropriate optional chaining and includes stable IDs for rendering.
205-233: LGTM! Past issues addressed.Loading and error states are now properly handled, and optional chaining prevents runtime crashes.
237-257: LGTM!Navigation implementation is correct. The
voidkeyword appropriately indicates the intentionally ignored promise.
joshunrau
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks good to m,e
works on issue #1103
allows for the statistics cards to be interacted with
shows number of users, available instrumentes or number of records for each instrument.
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.